/*
 * NPlot - A charting library for .NET
 * 
 * PlotSurface2D.cs
 * Copyright (C) 2003-2006 Matt Howlett and others.
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. Neither the name of NPlot nor the names of its contributors may
 *    be used to endorse or promote products derived from this software without
 *    specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;

namespace NPlot
{
    /// <summary>
    /// Implements the surface on which IDrawables are drawn. Is extended
    /// by Bitmap.PlotSurface2D, Windows.PlotSurface2D etc. TODO: better explanation.
    /// </summary>
    public class PlotSurface2D
    {
        /// <summary>
        /// Possible positions of the X axis.
        /// </summary>
        public enum XAxisPosition
        {
            /// <summary>
            /// X axis is on the top.
            /// </summary>
            Top = 1,
            //Center = 2,
            /// <summary>
            /// X axis is on the bottom.
            /// </summary>
            Bottom = 3,
        }

        /// <summary>
        /// Possible positions of the Y axis.
        /// </summary>
        public enum YAxisPosition
        {
            /// <summary>
            /// Y axis on the left.
            /// </summary>
            Left = 1,
            // Center
            /// <summary>
            /// Y axis on the right.
            /// </summary>
            Right = 3,
        }

        private readonly StringFormat m_titleDrawFormat;

        private Font m_titleFont;
        private string m_title;
        private Brush m_titleBrush;
        private int m_padding;
        private DateTimeAxis m_xAxis1;
        private LinearAxis m_yAxis1;
        private DateTimeAxis m_xAxis2;
        private LinearAxis m_yAxis2;
        private PhysicalAxis m_pXAxis1Cache;
        private PhysicalAxis m_pYAxis1Cache;
        private PhysicalAxis m_pXAxis2Cache;
        private PhysicalAxis m_pYAxis2Cache;
        private bool m_autoScaleAutoGeneratedAxes = false;
        private bool m_autoScaleTitle = false;

        private Rectangle m_plotAreaBoundingBoxCache = Rectangle.Empty;
        private Rectangle m_bbXAxis1Cache = Rectangle.Empty;
        private Rectangle m_bbXAxis2Cache = Rectangle.Empty;
        private Rectangle m_bbYAxis1Cache = Rectangle.Empty;
        private Rectangle m_bbYAxis2Cache = Rectangle.Empty;
        private Rectangle m_bbTitleCache;

        private Color m_plotBackColor;

        private List<IDrawable> m_drawables;
        private List<double> m_zPositions;
        private SortedList<double, int> m_ordering;

        private SmoothingMode m_smoothingMode;

        private List<AxesConstraint> m_axesConstraints = null;

        /// <summary>
        /// The physical bounding box of the last drawn plot surface area is available here.
        /// </summary>
        public Rectangle PlotAreaBoundingBoxCache
        {
            get
            {
                return m_plotAreaBoundingBoxCache;
            }
        }

        /// <summary>
        /// Performs a hit test with the given point and returns information 
        /// about the object being hit.
        /// </summary>
        /// <param name="p">The point to test.</param>
        /// <returns></returns>
        public ArrayList HitTest(Point p)
        {
            ArrayList a = new ArrayList();

            // this is the case if PlotSurface has been cleared.
            //if (m_bbXAxis1Cache == null)
            //{
            //    return a;
            //}
            if ((m_bbXAxis1Cache).Contains(p))
            {
                a.Add(m_xAxis1);
                return a;
            }
            else if ((m_bbYAxis1Cache).Contains(p))
            {
                a.Add(m_yAxis1);
                return a;
            }
            else if ((m_bbXAxis2Cache).Contains(p))
            {
                a.Add(m_xAxis2);
                return a;
            }
            else if ((m_bbYAxis2Cache).Contains(p))
            {
                a.Add(m_yAxis2);
                return a;
            }
            else if ((m_bbTitleCache).Contains(p))
            {
                a.Add(this);
                return a;
            }
            else if ((m_plotAreaBoundingBoxCache).Contains(p))
            {
                a.Add(this);
                return a;
            }

            return a;
        }


        /// <summary>
        /// The bottom abscissa axis.
        /// </summary>
        public DateTimeAxis XAxis1
        {
            get
            {
                return m_xAxis1;
            }
            set
            {
                m_xAxis1 = value;
            }
        }


        /// <summary>
        /// The left ordinate axis.
        /// </summary>
        public LinearAxis YAxis1
        {
            get
            {
                return m_yAxis1;
            }
            set
            {
                m_yAxis1 = value;
            }
        }


        /// <summary>
        /// The top abscissa axis.
        /// </summary>
        public DateTimeAxis XAxis2
        {
            get
            {
                return m_xAxis2;
            }
            set
            {
                m_xAxis2 = value;
            }
        }


        /// <summary>
        /// The right ordinate axis.
        /// </summary>
        public LinearAxis YAxis2
        {
            get
            {
                return m_yAxis2;
            }
            set
            {
                m_yAxis2 = value;
            }
        }


        /// <summary>
        /// The physical XAxis1 that was last drawn.
        /// </summary>
        public PhysicalAxis PhysicalXAxis1Cache
        {
            get
            {
                return m_pXAxis1Cache;
            }
        }


        /// <summary>
        /// The physical YAxis1 that was last drawn.
        /// </summary>
        public PhysicalAxis PhysicalYAxis1Cache
        {
            get
            {
                return m_pYAxis1Cache;
            }
        }


        /// <summary>
        /// The physical XAxis2 that was last drawn.
        /// </summary>
        public PhysicalAxis PhysicalXAxis2Cache
        {
            get
            {
                return m_pXAxis2Cache;
            }
        }


        /// <summary>
        /// The physical YAxis2 that was last drawn.
        /// </summary>
        public PhysicalAxis PhysicalYAxis2Cache
        {
            get
            {
                return m_pYAxis2Cache;
            }
        }

        /// <summary>
        /// The chart title.
        /// </summary>
        public string Title
        {
            get
            {
                return m_title;
            }
            set
            {
                m_title = value;
            }
        }

        /// <summary>
        /// The plot title font.
        /// </summary>
        public Font TitleFont
        {
            get
            {
                return m_titleFont;
            }
            set
            {
                m_titleFont = value;
            }
        }


        /// <summary>
        /// The distance in pixels to leave between of the edge of the bounding rectangle
        /// supplied to the Draw method, and the markings that make up the plot.
        /// </summary>
        public int Padding
        {
            get
            {
                return m_padding;
            }
            set
            {
                m_padding = value;
            }
        }

        /// <summary>
        /// Sets the title to be drawn using a solid brush of this color.
        /// </summary>
        public Color TitleColor
        {
            set
            {
                m_titleBrush = new SolidBrush(value);
            }
        }


        /// <summary>
        /// The brush used for drawing the title.
        /// </summary>
        public Brush TitleBrush
        {
            get
            {
                return m_titleBrush;
            }
            set
            {
                m_titleBrush = value;
            }
        }


        /// <summary>
        /// A color used to paint the plot background. Mutually exclusive with PlotBackImage and PlotBackBrush
        /// </summary>
        public Color PlotBackColor
        {
            set
            {
                m_plotBackColor = value;
            }
        }

        /// <summary>
        /// Smoothing mode to use when drawing plots.
        /// </summary>
        public SmoothingMode SmoothingMode
        {
            get
            {
                return m_smoothingMode;
            }
            set
            {
                m_smoothingMode = value;
            }
        }


        private void Init()
        {
            m_drawables = new List<IDrawable>();
            m_zPositions = new List<double>();
            m_ordering = new SortedList<double, int>();
            FontFamily fontFamily = new FontFamily("Arial");
            TitleFont = new Font(fontFamily, 14, FontStyle.Regular, GraphicsUnit.Pixel);
            m_padding = 10;
            m_title = "";
            m_autoScaleTitle = false;
            m_autoScaleAutoGeneratedAxes = false;
            m_xAxis1 = null;
            m_xAxis2 = null;
            m_yAxis1 = null;
            m_yAxis2 = null;
            m_pXAxis1Cache = null;
            m_pYAxis1Cache = null;
            m_pXAxis2Cache = null;
            m_pYAxis2Cache = null;
            m_titleBrush = new SolidBrush(Color.Black);
            m_plotBackColor = Color.White;

            m_smoothingMode = SmoothingMode.None;

            m_axesConstraints = new List<AxesConstraint>();
        }

        /// <summary>
        /// Default constructor.
        /// </summary>
        public PlotSurface2D()
        {
            // only create this once.
            m_titleDrawFormat = new StringFormat();
            m_titleDrawFormat.Alignment = StringAlignment.Center;

            Init();
        }

        private float DetermineScaleFactor(int w, int h)
        {
            float diag = (float)Math.Sqrt(w * w + h * h);
            float scaleFactor = (diag / 1400.0f) * 2.4f;

            if (scaleFactor > 1.0f)
            {
                return scaleFactor;
            }
            else
            {
                return 1.0f;
            }
        }

        /// <summary>
        /// Adds a drawable object to the plot surface with z-order 0. If the object is an IPlot,
        /// the PlotSurface2D axes will also be updated. 
        /// </summary>
        /// <param name="p">The IDrawable object to add to the plot surface.</param>
        public void Add(IDrawable p)
        {
            Add(p, 0);
        }


        /// <summary>
        /// Adds a drawable object to the plot surface. If the object is an IPlot, 
        /// the PlotSurface2D axes will also be updated.
        /// </summary>
        /// <param name="p">The IDrawable object to add to the plot surface.</param>
        /// <param name="zOrder">The z-ordering when drawing (objects with lower numbers are drawn first)</param>
        public void Add(IDrawable p, int zOrder)
        {
            m_drawables.Add(p);
            m_zPositions.Add((double)zOrder);
            // fraction is to make key unique. With 10 million plots at same z, this buggers up.. 
            double fraction = (double)(++uniqueCounter_) / 10000000.0f;
            m_ordering.Add((double)zOrder + fraction, m_drawables.Count - 1);

            // if p is just an IDrawable, then it can't affect the axes.
            if (p is IPlot)
            {
                UpdateAxes(false);
            }
        }

        private int uniqueCounter_ = 0;

        private void UpdateAxes(bool recalculateAll)
        {
            int position = 0;

            // if we're not recalculating axes using all iplots then set
            // position to last one in list.
            if (!recalculateAll)
            {
                position = m_drawables.Count - 1;
                if (position < 0) position = 0;
            }

            if (recalculateAll)
            {
                m_xAxis1 = null;
                m_yAxis1 = null;
                m_xAxis2 = null;
                m_yAxis2 = null;
            }

            for (int i = position; i < m_drawables.Count; ++i)
            {
                // only update axes if this drawable is an IPlot.
                if (!(m_drawables[i] is IPlot))
                    continue;

                IPlot p = (IPlot)m_drawables[i];

                if (m_xAxis1 == null)
                {
                    m_xAxis1 = p.SuggestXAxis();
                    if (m_xAxis1 != null)
                    {
                        m_xAxis1.TicksAngle = -(float)Math.PI / 2.0f;
                    }
                }
                else
                {
                    m_xAxis1.LUB(p.SuggestXAxis());
                }

                if (m_xAxis1 != null)
                {
                    m_xAxis1.MinPhysicalLargeTickStep = 50;

                    if (AutoScaleAutoGeneratedAxes)
                    {
                        m_xAxis1.AutoScaleText = true;
                        m_xAxis1.AutoScaleTicks = true;
                        m_xAxis1.TicksIndependentOfPhysicalExtent = true;
                    }
                    else
                    {
                        m_xAxis1.AutoScaleText = false;
                        m_xAxis1.AutoScaleTicks = false;
                        m_xAxis1.TicksIndependentOfPhysicalExtent = false;
                    }
                }


                if (m_yAxis1 == null)
                {
                    m_yAxis1 = p.SuggestYAxis();
                    if (m_yAxis1 != null)
                    {
                        m_yAxis1.TicksAngle = (float)Math.PI / 2.0f;
                    }
                }
                else
                {
                    m_yAxis1.LUB(p.SuggestYAxis());
                }

                if (m_yAxis1 != null)
                {
                    if (AutoScaleAutoGeneratedAxes)
                    {
                        m_yAxis1.AutoScaleText = true;
                        m_yAxis1.AutoScaleTicks = true;
                        m_yAxis1.TicksIndependentOfPhysicalExtent = true;
                    }
                    else
                    {
                        m_yAxis1.AutoScaleText = false;
                        m_yAxis1.AutoScaleTicks = false;
                        m_yAxis1.TicksIndependentOfPhysicalExtent = false;
                    }
                }
            }
        }

        private void DetermineAxesToDraw(out Axis xAxis1, out Axis xAxis2, out Axis yAxis1, out Axis yAxis2)
        {
            xAxis1 = m_xAxis1;
            xAxis2 = m_xAxis2;
            yAxis1 = m_yAxis1;
            yAxis2 = m_yAxis2;

            if (m_xAxis1 == null)
            {
                if (m_xAxis2 == null)
                {
                    throw new NPlotException("Error: No X-Axis specified");
                }
                xAxis1 = (Axis)m_xAxis2.Clone();
                xAxis1.HideTickText = true;
                xAxis1.TicksAngle = -(float)Math.PI / 2.0f;
            }

            if (m_xAxis2 == null)
            {
                // don't need to check if xAxis1_ == null, as case already handled above.
                xAxis2 = (Axis)m_xAxis1.Clone();
                xAxis2.HideTickText = true;
                xAxis2.TicksAngle = (float)Math.PI / 2.0f;
            }

            if (m_yAxis1 == null)
            {
                if (m_yAxis2 == null)
                {
                    throw new NPlotException("Error: No Y-Axis specified");
                }
                yAxis1 = (Axis)m_yAxis2.Clone();
                yAxis1.HideTickText = true;
                yAxis1.TicksAngle = (float)Math.PI / 2.0f;
            }

            if (m_yAxis2 == null)
            {
                // don't need to check if yAxis1_ == null, as case already handled above.
                yAxis2 = (Axis)m_yAxis1.Clone();
                yAxis2.HideTickText = true;
                yAxis2.TicksAngle = -(float)Math.PI / 2.0f;
            }
        }

        private void DeterminePhysicalAxesToDraw(Rectangle bounds, Axis xAxis1, Axis xAxis2, Axis yAxis1, Axis yAxis2,
                                                 out PhysicalAxis pXAxis1, out PhysicalAxis pXAxis2, out PhysicalAxis pYAxis1, out PhysicalAxis pYAxis2)
        {
            Rectangle cb = bounds;

            pXAxis1 = new PhysicalAxis(xAxis1, new Point(cb.Left, cb.Bottom), new Point(cb.Right, cb.Bottom));
            pYAxis1 = new PhysicalAxis(yAxis1, new Point(cb.Left, cb.Bottom), new Point(cb.Left, cb.Top));
            pXAxis2 = new PhysicalAxis(xAxis2, new Point(cb.Left, cb.Top), new Point(cb.Right, cb.Top));
            pYAxis2 = new PhysicalAxis(yAxis2, new Point(cb.Right, cb.Bottom), new Point(cb.Right, cb.Top));

            int bottomIndent = m_padding;
            if (!pXAxis1.Axis.Hidden)
            {
                // evaluate its bounding box
                Rectangle bb = pXAxis1.GetBoundingBox();
                // finally determine its indentation from the bottom
                bottomIndent = bottomIndent + bb.Bottom - cb.Bottom;
            }

            int leftIndent = m_padding;
            if (!pYAxis1.Axis.Hidden)
            {
                // evaluate its bounding box
                Rectangle bb = pYAxis1.GetBoundingBox();
                // finally determine its indentation from the left
                leftIndent = leftIndent - bb.Left + cb.Left;
            }

            int topIndent = m_padding;
            float scale = DetermineScaleFactor(bounds.Width, bounds.Height);
            int titleHeight;
            if (AutoScaleTitle)
            {
                titleHeight = Utils.ScaleFont(m_titleFont, scale).Height;
            }
            else
            {
                titleHeight = m_titleFont.Height;
            }

            //count number of new lines in title.
            int nlCount = 0;
            for (int i = 0; i < m_title.Length; ++i)
            {
                if (m_title[i] == '\n')
                    nlCount += 1;
            }
            titleHeight = (int)(((float)nlCount * 0.75 + 1.0f) * (float)titleHeight);

            if (!pXAxis2.Axis.Hidden)
            {
                // evaluate its bounding box
                Rectangle bb = pXAxis2.GetBoundingBox();
                topIndent = topIndent - bb.Top + cb.Top;

                // finally determine its indentation from the top
                // correct top indendation to take into account plot title
                if (m_title != "")
                {
                    topIndent += (int)(titleHeight * 1.3f);
                }
            }

            int rightIndent = m_padding;
            if (!pYAxis2.Axis.Hidden)
            {
                // evaluate its bounding box
                Rectangle bb = pYAxis2.GetBoundingBox();

                // finally determine its indentation from the right
                rightIndent = (int)(rightIndent + bb.Right - cb.Right);
            }

            // now we have all the default calculated positions and we can proceed to
            // "move" the axes to their right places

            // primary axes (bottom, left)
            pXAxis1.PhysicalMin = new Point(cb.Left + leftIndent, cb.Bottom - bottomIndent);
            pXAxis1.PhysicalMax = new Point(cb.Right - rightIndent, cb.Bottom - bottomIndent);
            pYAxis1.PhysicalMin = new Point(cb.Left + leftIndent, cb.Bottom - bottomIndent);
            pYAxis1.PhysicalMax = new Point(cb.Left + leftIndent, cb.Top + topIndent);

            // secondary axes (top, right)
            pXAxis2.PhysicalMin = new Point(cb.Left + leftIndent, cb.Top + topIndent);
            pXAxis2.PhysicalMax = new Point(cb.Right - rightIndent, cb.Top + topIndent);
            pYAxis2.PhysicalMin = new Point(cb.Right - rightIndent, cb.Bottom - bottomIndent);
            pYAxis2.PhysicalMax = new Point(cb.Right - rightIndent, cb.Top + topIndent);
        }


        /// <summary>
        /// Draw the the PlotSurface2D and all contents [axes, drawables] on the 
        /// supplied graphics surface.
        /// </summary>
        /// <param name="g">The graphics surface on which to draw.</param>
        /// <param name="bounds">A bounding box on this surface that denotes the area on the
        /// surface to confine drawing to.</param>
        public void Draw(Graphics g, Rectangle bounds)
        {
            // determine font sizes and tick scale factor.
            float scale = DetermineScaleFactor(bounds.Width, bounds.Height);

            // if there is nothing to plot, return.
            if (m_drawables.Count == 0)
            {
                // draw title
                float x_center = (bounds.Left + bounds.Right) / 2.0f;
                float y_center = (bounds.Top + bounds.Bottom) / 2.0f;
                Font scaled_font;
                if (AutoScaleTitle)
                {
                    scaled_font = Utils.ScaleFont(m_titleFont, scale);
                }
                else
                {
                    scaled_font = m_titleFont;
                }
                g.DrawString(m_title, scaled_font, this.m_titleBrush, new PointF(x_center, y_center), m_titleDrawFormat);

                return;
            }

            Stopwatch sw = StepTimer.Start("Determine Axis");

            // determine the [non physical] axes to draw based on the axis properties set.
            Axis xAxis1, xAxis2, yAxis1, yAxis2;
            DetermineAxesToDraw(out xAxis1, out xAxis2, out yAxis1, out yAxis2);

            // apply scale factor to axes as desired.
            if (xAxis1.AutoScaleTicks)
                xAxis1.TickScale = scale;
            if (xAxis1.AutoScaleText)
                xAxis1.FontScale = scale;
            if (yAxis1.AutoScaleTicks)
                yAxis1.TickScale = scale;
            if (yAxis1.AutoScaleText)
                yAxis1.FontScale = scale;
            if (xAxis2.AutoScaleTicks)
                xAxis2.TickScale = scale;
            if (xAxis2.AutoScaleText)
                xAxis2.FontScale = scale;
            if (yAxis2.AutoScaleTicks)
                yAxis2.TickScale = scale;
            if (yAxis2.AutoScaleText)
                yAxis2.FontScale = scale;

            // determine the default physical positioning of those axes.
            PhysicalAxis pXAxis1, pYAxis1, pXAxis2, pYAxis2;
            DeterminePhysicalAxesToDraw(bounds, xAxis1, xAxis2, yAxis1, yAxis2, out pXAxis1, out pXAxis2, out pYAxis1, out pYAxis2);

            StepTimer.Stop(sw);

            float oldXAxis2Height = pXAxis2.PhysicalMin.Y;

            // Apply axes constraints
            for (int i = 0; i < m_axesConstraints.Count; ++i)
            {
                (m_axesConstraints[i]).ApplyConstraint(pXAxis1, pYAxis1, pXAxis2, pYAxis2);
            }

            float newXAxis2Height = pXAxis2.PhysicalMin.Y;

            float titleExtraOffset = oldXAxis2Height - newXAxis2Height;

            sw = StepTimer.Start("Draw Axis");

            // now we are ready to define the bounding box for the plot area (to use in clipping
            // operations.
            m_plotAreaBoundingBoxCache = new Rectangle(
                Math.Min(pXAxis1.PhysicalMin.X, pXAxis1.PhysicalMax.X),
                Math.Min(pYAxis1.PhysicalMax.Y, pYAxis1.PhysicalMin.Y),
                Math.Abs(pXAxis1.PhysicalMax.X - pXAxis1.PhysicalMin.X + 1),
                Math.Abs(pYAxis1.PhysicalMin.Y - pYAxis1.PhysicalMax.Y + 1)
                );
            m_bbXAxis1Cache = pXAxis1.GetBoundingBox();
            m_bbXAxis2Cache = pXAxis2.GetBoundingBox();
            m_bbYAxis1Cache = pYAxis1.GetBoundingBox();
            m_bbYAxis2Cache = pYAxis2.GetBoundingBox();

            // Fill in the background. 
            g.FillRectangle(new SolidBrush(m_plotBackColor), m_plotAreaBoundingBoxCache);

            // draw title
            float xt = (pXAxis2.PhysicalMax.X + pXAxis2.PhysicalMin.X) / 2.0f;
            float yt = bounds.Top + m_padding - titleExtraOffset;
            Font scaledFont;
            if (AutoScaleTitle)
            {
                scaledFont = Utils.ScaleFont(m_titleFont, scale);
            }
            else
            {
                scaledFont = m_titleFont;
            }
            g.DrawString(m_title, scaledFont, m_titleBrush, new PointF(xt, yt), m_titleDrawFormat);

            //count number of new lines in title.
            int nlCount = 0;
            for (int i = 0; i < m_title.Length; ++i)
            {
                if (m_title[i] == '\n')
                    nlCount += 1;
            }

            SizeF s = g.MeasureString(m_title, scaledFont);
            m_bbTitleCache = new Rectangle((int)(xt - s.Width / 2), (int)(yt), (int)(s.Width), (int)(s.Height) * (nlCount + 1));

            StepTimer.Stop(sw);

            //sw = StepTimer.Start("Draw IDrawables");

            // draw drawables..
            SmoothingMode smoothSave = g.SmoothingMode;

            g.SmoothingMode = m_smoothingMode;

            for (int i_o = 0; i_o < m_ordering.Count; ++i_o)
            {
                int i = m_ordering.Values[i_o];

                IDrawable drawable = m_drawables[i];

                PhysicalAxis drawXAxis;
                PhysicalAxis drawYAxis;

                drawXAxis = pXAxis1;
                drawYAxis = pYAxis1;

                // set the clipping region.. (necessary for zoom)
                g.Clip = new Region(m_plotAreaBoundingBoxCache);
                // plot.
                drawable.Draw(g, drawXAxis, drawYAxis);
                // reset it..
                g.ResetClip();
            }

            //StepTimer.Stop(sw);

            // cache the physical axes we used on this draw;
            m_pXAxis1Cache = pXAxis1;
            m_pYAxis1Cache = pYAxis1;
            m_pXAxis2Cache = pXAxis2;
            m_pYAxis2Cache = pYAxis2;

            g.SmoothingMode = smoothSave;

            sw = StepTimer.Start("Draw Axis Final");
            // now draw axes.
            Rectangle axisBounds;
            pXAxis1.Draw(g, out axisBounds);
            pXAxis2.Draw(g, out axisBounds);
            pYAxis1.Draw(g, out axisBounds);
            pYAxis2.Draw(g, out axisBounds);

            StepTimer.Stop(sw);
        }


        /// <summary>
        /// Clears the plot and resets all state to the default.
        /// </summary>
        public void Clear()
        {
            Init();
        }

        /// <summary>
        /// Add an axis constraint to the plot surface. Axes constraints give you 
        /// control over where NPlot positions each axes, and the world - pixel
        /// ratio.
        /// </summary>
        /// <param name="constraint">The axis constraint to add.</param>
        public void AddAxesConstraint(AxesConstraint constraint)
        {
            m_axesConstraints.Add(constraint);
        }


        /// <summary>
        /// Whether or not the title will be scaled according to size of the plot surface.
        /// </summary>
        public bool AutoScaleTitle
        {
            get
            {
                return m_autoScaleTitle;
            }
            set
            {
                m_autoScaleTitle = value;
            }
        }


        /// <summary>
        /// When plots are added to the plot surface, the axes they are attached to
        /// are immediately modified to reflect data of the plot. If 
        /// AutoScaleAutoGeneratedAxes is true when a plot is added, the axes will
        /// be turned in to auto scaling ones if they are not already [tick marks,
        /// tick text and label size scaled to size of plot surface]. If false,
        /// axes will not be autoscaling.
        /// </summary>
        public bool AutoScaleAutoGeneratedAxes
        {
            get
            {
                return m_autoScaleAutoGeneratedAxes;
            }
            set
            {
                m_autoScaleAutoGeneratedAxes = value;
            }
        }


        /// <summary>
        /// Remove a drawable object. 
        /// Note that axes are not updated.
        /// </summary>
        /// <param name="p">Drawable to remove.</param>
        /// <param name="updateAxes">if true, the axes are updated.</param>
        public void Remove(IDrawable p, bool updateAxes)
        {
            int index = m_drawables.IndexOf(p);
            if (index < 0)
                return;
            m_drawables.RemoveAt(index);
            m_zPositions.RemoveAt(index);

            if (updateAxes)
            {
                UpdateAxes(true);
            }

            RefreshZOrdering();
        }


        /// <summary>
        /// If a plot is removed, then the ordering_ list needs to be 
        /// recalculated. 
        /// </summary>
        private void RefreshZOrdering()
        {
            uniqueCounter_ = 0;
            m_ordering = new SortedList<double, int>();
            for (int i = 0; i < m_zPositions.Count; ++i)
            {
                double zpos = m_zPositions[i];
                double fraction = (double)(++uniqueCounter_) / 10000000.0f;
                double d = zpos + fraction;
                m_ordering.Add(d, i);
            }
        }


        /// <summary>
        /// Gets an array list containing all drawables currently added to the PlotSurface2D.
        /// </summary>
        public List<IDrawable> Drawables
        {
            get
            {
                return m_drawables;
            }
        }
    }
}